/*
 * Copyright 2011 Witoslaw Koczewsi <wi@koczewski.de>, Artjom Kochtchi
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero
 * General Public License as published by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
 * License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package scrum.server.project;

import ilarkesto.base.Money;
import ilarkesto.base.Str;
import ilarkesto.base.Utl;
import ilarkesto.core.time.Date;
import ilarkesto.core.time.DateAndTime;
import ilarkesto.core.time.Time;
import ilarkesto.persistence.AEntity;
import ilarkesto.persistence.Persist;
import ilarkesto.rss.Rss20Builder;
import ilarkesto.search.Searchable;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import scrum.client.common.WeekdaySelector;
import scrum.server.KunagiRootConfig;
import scrum.server.ScrumWebApplication;
import scrum.server.admin.ProjectUserConfig;
import scrum.server.admin.User;
import scrum.server.calendar.SimpleEvent;
import scrum.server.collaboration.Comment;
import scrum.server.collaboration.CommentDao;
import scrum.server.collaboration.Subject;
import scrum.server.collaboration.Wikipage;
import scrum.server.estimation.RequirementEstimationVote;
import scrum.server.files.File;
import scrum.server.impediments.Impediment;
import scrum.server.issues.Issue;
import scrum.server.journal.ProjectEvent;
import scrum.server.pr.BlogEntry;
import scrum.server.release.Release;
import scrum.server.risks.Risk;
import scrum.server.sprint.Sprint;
import scrum.server.sprint.SprintDaySnapshot;
import scrum.server.sprint.SprintReport;
import scrum.server.sprint.SprintReportDao;
import scrum.server.sprint.Task;
import scrum.server.sprint.TaskDao;

public class Project extends GProject {

	private transient Comparator<Requirement> requirementsOrderComparator;

	private static final String NEXT_SPRINT_LABEL = "Next Sprint";
	private static final String CURRENT_SPRINT_LABEL = "Current Sprint";

	// --- dependencies ---

	private static transient ProjectSprintSnapshotDao projectSprintSnapshotDao;
	private static transient TaskDao taskDao;
	private static transient KunagiRootConfig config;
	private static transient CommentDao commentDao;
	private static transient SprintReportDao sprintReportDao;
	private static transient ScrumWebApplication webApplication;

	public static void setWebApplication(ScrumWebApplication webApplication) {
		Project.webApplication = webApplication;
	}

	public static void setSprintReportDao(SprintReportDao sprintReportDao) {
		Project.sprintReportDao = sprintReportDao;
	}

	public static void setCommentDao(CommentDao commentDao) {
		Project.commentDao = commentDao;
	}

	public static void setConfig(KunagiRootConfig config) {
		Project.config = config;
	}

	public static void setTaskDao(TaskDao taskDao) {
		Project.taskDao = taskDao;
	}

	public static void setProjectSprintSnapshotDao(ProjectSprintSnapshotDao projectSprintSnapshotDao) {
		Project.projectSprintSnapshotDao = projectSprintSnapshotDao;
	}

	// --- ---

	public boolean isInHistory(Requirement requirement) {
		for (SprintReport report : getSprintReports()) {
			if (report.containsCompletedRequirement(requirement)) return true;
			if (report.containsRejectedRequirement(requirement)) return true;
		}
		return false;
	}

	public Set<Sprint> getRelevantSprints() {
		Set<Sprint> ret = new HashSet<Sprint>();
		ret.add(getCurrentSprint());
		ret.add(getNextSprint());
		return ret;
	}

	public Set<Requirement> getProductBacklogRequirements() {
		Set<Requirement> requirements = getRequirements();
		Iterator<Requirement> iterator = requirements.iterator();
		while (iterator.hasNext()) {
			Requirement requirement = iterator.next();
			if (requirement.isDeleted() || requirement.isClosed() || requirement.isInCurrentSprint())
				iterator.remove();
		}
		return requirements;
	}

	public Set<SprintReport> getSprintReports() {
		return sprintReportDao.getSprintReportsByProject(this);
	}

	public void moveRequirementToTop(Requirement requirement) {
		List<String> orderIds = getRequirementsOrderIds();
		String id = requirement.getId();
		orderIds.remove(id);
		orderIds.add(0, id);
		setRequirementsOrderIds(orderIds);
	}

	public void addRequirementsOrderId(int index, String id) {
		List<String> ids = getRequirementsOrderIds();
		ids.add(index, id);
		setRequirementsOrderIds(ids);
	}

	public WeekdaySelector getFreeDaysAsWeekdaySelector() {
		return new WeekdaySelector(getFreeDays());
	}

	public String getUsersRolesAsString(User user, String prefix, String suffix) {
		StringBuilder sb = new StringBuilder();
		List<String> roles = new ArrayList<String>();
		if (containsProductOwner(user)) roles.add("PO");
		if (containsScrumMaster(user)) roles.add("SM");
		if (containsTeamMember(user)) roles.add("T");
		boolean first = true;
		if (!roles.isEmpty()) {
			for (String role : roles) {
				if (first) {
					first = false;
					if (prefix != null) sb.append(prefix);
				} else {
					sb.append(",");
				}
				sb.append(role);
			}
			if (suffix != null) sb.append(suffix);
		}
		return sb.toString();
	}

	public boolean containsParticipantWithVerifiedEmail() {
		for (User user : getParticipants()) {
			if (user.isEmailVerified()) return true;
		}
		return false;
	}

	public void updateRequirementsOrder(List<Requirement> requirements) {
		setRequirementsOrderIds(Persist.getIdsAsList(requirements));
	}

	public synchronized void updateHomepage() {
		if (!isHomepageDirSet()) return;
		getHomepageUpdater().processAll();
	}

	public synchronized void updateHomepage(AEntity entity) {
		if (!isHomepageDirSet()) return;
		getHomepageUpdater().processEntityTemplate(entity);
	}

	public java.io.File getHomepageDirFile() {
		if (!isHomepageDirSet()) return null;
		return new java.io.File(getHomepageDir());
	}

	public String getHomepageVelocityDir() {
		if (!isHomepageDirSet()) return null;
		return getHomepageDir() + "/velocity";
	}

	public String getHomepageScriptsDir() {
		if (!isHomepageDirSet()) return null;
		return getHomepageDir() + "/scripts";
	}

	public HomepageUpdater getHomepageUpdater() {
		return new HomepageUpdater(this);
	}

	public void scanFiles() {
		java.io.File dir = new java.io.File(getFileRepositoryPath());
		java.io.File[] files = dir.listFiles();
		if (files != null) {

			for (java.io.File f : files) {
				File file = fileDao.getFilesByName(f.getName(), this);
				if (file == null) {
					file = fileDao.postFile(f, this);
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	public ArrayList<AEntity> search(String text) {
		String[] keys = Str.tokenizeToArray(text, " ");
		ArrayList ret = new ArrayList();
		ret.addAll(getMatching(getRequirements(), keys));
		ret.addAll(getMatching(getQualitys(), keys));
		ret.addAll(getMatching(getScenarios(), keys));
		ret.addAll(getMatching(getTasks(), keys));
		ret.addAll(getMatching(getWikipages(), keys));
		ret.addAll(getMatching(getIssues(), keys));
		ret.addAll(getMatching(getImpediments(), keys));
		ret.addAll(getMatching(getRisks(), keys));
		ret.addAll(getMatching(getFiles(), keys));
		ret.addAll(getMatching(getReleases(), keys));
		ret.addAll(getMatching(getBlogEntrys(), keys));
		return ret;
	}

	private <T extends Searchable> List<T> getMatching(Collection<T> entities, String[] keys) {
		List<T> ret = new ArrayList<T>();
		for (T entity : entities) {
			if (matchesKeys(entity, keys)) ret.add(entity);
		}
		return ret;
	}

	private boolean matchesKeys(Searchable entity, String[] keys) {
		for (String key : keys) {
			if (!entity.matchesKey(key)) return false;
		}
		return true;
	}

	public void writeJournalAsRss(OutputStream out, String encoding) {
		Rss20Builder rss = new Rss20Builder();
		rss.setTitle(getLabel() + " Event Journal");
		rss.setLanguage("en");
		rss.setLink(webApplication.createUrl(this, null));
		for (ProjectEvent event : getLatestProjectEvents(30)) {
			Rss20Builder.Item item = rss.addItem();
			item.setTitle(event.getLabel());
			item.setDescription(event.getLabel());
			String link = webApplication.createUrl(this, event.getSubject()) + "|fromEvent=" + event.getId();
			item.setLink(link);
			item.setGuid(link);
			item.setPubDate(event.getDateAndTime());
		}
		rss.sortItems();
		rss.write(out, encoding);
	}

	public String getFileRepositoryPath() {
		return config.getFileRepositoryPath() + "/" + getId();
	}

	public Set<SimpleEvent> getCalendarEvents() {
		return simpleEventDao.getSimpleEventsByProject(this);
	}

	public List<ProjectEvent> getLatestProjectEvents(int min) {
		List<ProjectEvent> events = Utl.sort(projectEventDao.getProjectEventsByProject(this));

		DateAndTime deadline = new DateAndTime(Date.today().prevDay(), Time.now());
		List<ProjectEvent> ret = new ArrayList<ProjectEvent>();
		int count = 0;
		for (ProjectEvent event : events) {
			ret.add(event);
			count++;
			DateAndTime dateAndTime = event.getDateAndTime();
			if (count > min && dateAndTime.isBefore(deadline)) break;
		}
		return ret;
	}

	public Set<ProjectUserConfig> getUserConfigs() {
		Set<ProjectUserConfig> configs = new HashSet<ProjectUserConfig>();
		for (User user : getParticipants()) {
			configs.add(getUserConfig(user));
		}
		return configs;
	}

	public ProjectUserConfig getUserConfig(User user) {
		return projectUserConfigDao.getProjectUserConfig(this, user);
	}

	public Set<Task> getTasks() {
		return taskDao.getTasksByProject(this);
	}

	public AEntity getEntityByReference(String reference) {
		if (reference.length() > 4 && reference.startsWith("[[")) {
			String pageName = reference.substring(2, reference.length() - 2);
			return getWikipageByName(pageName);
		}

		int number = Integer.parseInt(reference.substring(scrum.client.project.Requirement.REFERENCE_PREFIX.length()));

		if (reference.startsWith(scrum.client.project.Requirement.REFERENCE_PREFIX)) {
			return getRequirementByNumber(number);
		} else if (reference.startsWith(scrum.client.project.Quality.REFERENCE_PREFIX)) {
			return getQualityByNumber(number);
		} else if (reference.startsWith(scrum.client.project.Scenario.REFERENCE_PREFIX)) {
			return getScenarioByNumber(number);
		} else if (reference.startsWith(scrum.client.sprint.Task.REFERENCE_PREFIX)) {
			return getTaskByNumber(number);
		} else if (reference.startsWith(scrum.client.impediments.Impediment.REFERENCE_PREFIX)) {
			return getImpedimentByNumber(number);
		} else if (reference.startsWith(scrum.client.issues.Issue.REFERENCE_PREFIX)) {
			return getIssueByNumber(number);
		} else if (reference.startsWith(scrum.client.sprint.Sprint.REFERENCE_PREFIX)) {
			return getSprintByNumber(number);
		} else if (reference.startsWith(scrum.client.collaboration.Subject.REFERENCE_PREFIX)) {
			return getSubjectByNumber(number);
		} else if (reference.startsWith(scrum.client.files.File.REFERENCE_PREFIX)) {
			return getFileByNumber(number);
		} else if (reference.startsWith(scrum.client.calendar.SimpleEvent.REFERENCE_PREFIX)) {
			return getSimpleEventByNumber(number);
		} else if (reference.startsWith(scrum.client.release.Release.REFERENCE_PREFIX)) {
			return getReleaseByNumber(number);
		} else if (reference.startsWith(scrum.client.pr.BlogEntry.REFERENCE_PREFIX)) { return getBlogEntryByNumber(number); }

		return null;
	}

	public Requirement getRequirementByNumber(int number) {
		return requirementDao.getRequirementByNumber(number, this);
	}

	public Task getTaskByNumber(int number) {
		return taskDao.getTaskByNumber(number, this);
	}

	public Quality getQualityByNumber(int number) {
		return qualityDao.getQualityByNumber(number, this);
	}

	public Scenario getScenarioByNumber(int number) {
		return scenarioDao.getScenarioByNumber(number, this);
	}

	public Issue getIssueByNumber(int number) {
		return issueDao.getIssueByNumber(number, this);
	}

	public Sprint getSprintByNumber(int number) {
		return sprintDao.getSprintByNumber(number, this);
	}

	public Impediment getImpedimentByNumber(int number) {
		return impedimentDao.getImpedimentByNumber(number, this);
	}

	public Subject getSubjectByNumber(int number) {
		return subjectDao.getSubjectsByNumber(number, this);
	}

	public File getFileByNumber(int number) {
		return fileDao.getFileByNumber(number, this);
	}

	public File getFileByReference(String reference) {
		return getFileByNumber(Integer.parseInt(reference.substring(3)));
	}

	public SimpleEvent getSimpleEventByNumber(int number) {
		return simpleEventDao.getSimpleEventByNumber(number, this);
	}

	public Release getReleaseByNumber(int number) {
		return releaseDao.getReleaseByNumber(number, this);
	}

	public BlogEntry getBlogEntryByNumber(int number) {
		return blogEntryDao.getBlogEntryByNumber(number, this);
	}

	public Wikipage getWikipageByName(String name) {
		return wikipageDao.getWikipageByName(name, this);
	}

	public synchronized int generateTaskNumber() {
		int number = getLastTaskNumber() + 1;
		setLastTaskNumber(number);
		return number;
	}

	public synchronized int generateEventNumber() {
		int number = getLastEventNumber() + 1;
		setLastEventNumber(number);
		return number;
	}

	public synchronized int generateFileNumber() {
		int number = getLastFileNumber() + 1;
		setLastFileNumber(number);
		return number;
	}

	public synchronized int generateRequirementNumber() {
		int number = getLastRequirementNumber() + 1;
		setLastRequirementNumber(number);
		return number;
	}

	public synchronized int generateImpedimentNumber() {
		int number = getLastImpedimentNumber() + 1;
		setLastImpedimentNumber(number);
		return number;
	}

	public synchronized int generateSubjectNumber() {
		int number = getLastSubjectNumber() + 1;
		setLastSubjectNumber(number);
		return number;
	}

	public synchronized int generateRiskNumber() {
		int number = getLastRiskNumber() + 1;
		setLastRiskNumber(number);
		return number;
	}

	public synchronized int generateIssueNumber() {
		int number = getLastIssueNumber() + 1;
		setLastIssueNumber(number);
		return number;
	}

	public synchronized int generateSprintNumber() {
		int number = getLastSprintNumber() + 1;
		setLastSprintNumber(number);
		return number;
	}

	public synchronized int generateReleaseNumber() {
		int number = getLastReleaseNumber() + 1;
		setLastReleaseNumber(number);
		return number;
	}

	public synchronized int generateQualityNumber() {
		int number = getLastQualityNumber() + 1;
		setLastQualityNumber(number);
		return number;
	}

	public synchronized int generateScenarioNumber() {
		int number = getLastScenarioNumber() + 1;
		setLastScenarioNumber(number);
		return number;
	}

	public synchronized int generateBlogEntryNumber() {
		int number = getLastBlogEntryNumber() + 1;
		setLastBlogEntryNumber(number);
		return number;
	}

	public Release getCurrentRelease() {
		return releaseDao.getCurrentRelease(this);
	}

	public Release getNextRelease() {
		return releaseDao.getNextRelease(this);
	}

	public ProjectSprintSnapshot getCurrentSprintSnapshot() {
		ProjectSprintSnapshot snapshot = projectSprintSnapshotDao.getProjectSprintSnapshotBySprint(getCurrentSprint());
		if (snapshot == null) snapshot = createSprintSnapshot();
		return snapshot;
	}

	// public int getRemainingWork() {
	// int sum = 0;
	// for (Requirement requirement : getRequirements()) {
	// if (requirement.isClosed()) continue;
	// Integer work = requirement.getEstimatedWork();
	// if (work != null) sum += work;
	// }
	// return sum;
	// }
	//
	// public int getBurnedWork() {
	// int sum = 0;
	// for (Requirement requirement : getRequirements()) {
	// if (!requirement.isClosed()) continue;
	// Integer work = requirement.getEstimatedWork();
	// if (work != null) sum += work;
	// }
	// return sum;
	// }

	public List<ProjectSprintSnapshot> getSprintSnapshots() {
		return projectSprintSnapshotDao.getProjectSprintSnapshotsByProject(this);
	}

	public Sprint switchToNextSprint() {
		Sprint oldSprint = getCurrentSprint();
		oldSprint.close();
		oldSprint.setEnd(Date.today());

		getCurrentSprintSnapshot().update();

		Sprint newSprint = getNextSprint();
		if (newSprint == null) newSprint = createNextSprint();
		if (!newSprint.isBeginSet() || newSprint.getBegin().isPast()) newSprint.setBegin(Date.today());
		if (!newSprint.isEndSet() || newSprint.getEnd().isBeforeOrSame(newSprint.getBegin()))
			newSprint.setEnd(newSprint.getBegin().addDays(oldSprint.getLengthInDays()));
		newSprint.setOriginallyEnd(newSprint.getEnd());

		if (newSprint.isLabel(NEXT_SPRINT_LABEL)) newSprint.setLabel(CURRENT_SPRINT_LABEL);
		setCurrentSprint(newSprint);
		createNextSprint();

		createSprintSnapshot();

		for (Task task : oldSprint.getTasks()) {
			if (task.isClosed()) {
				taskDao.deleteEntity(task);
			}
		}

		return newSprint;
	}

	private ProjectSprintSnapshot createSprintSnapshot() {
		ProjectSprintSnapshot snapshot = projectSprintSnapshotDao.newEntityInstance();
		snapshot.setSprint(getCurrentSprint());
		snapshot.update();
		projectSprintSnapshotDao.saveEntity(snapshot);
		return snapshot;
	}

	public Sprint createNextSprint() {
		Sprint sprint = sprintDao.newEntityInstance();
		sprint.setProject(this);
		sprint.setLabel(NEXT_SPRINT_LABEL);
		if (isCurrentSprintSet()) {
			sprint.setBegin(getCurrentSprint().getEnd());
			Integer length = getCurrentSprint().getLengthInDays();
			if (length != null) sprint.setEnd(sprint.getBegin().addDays(length));
		}
		sprintDao.saveEntity(sprint);
		setNextSprint(sprint);
		return sprint;
	}

	public Set<Issue> getAcceptedIssues() {
		return issueDao.getAcceptedIssues(this);
	}

	public Set<Issue> getClosedIssues() {
		return issueDao.getClosedIssues(this);
	}

	public Set<Issue> getOpenIssues() {
		return issueDao.getOpenIssues(this);
	}

	public Set<Issue> getPublishedIssues() {
		return issueDao.getPublishedIssues(this);
	}

	public Set<Issue> getBugsInCurrentRelease() {
		Release release = getCurrentRelease();
		Release nextRelease = getNextRelease();
		if (release == null) return getOpenBugs();
		Set<Issue> ret = new HashSet<Issue>();
		ret.addAll(getOpenBugs());
		for (Issue issue : getClosedIssues()) {
			if (issue.containsFixRelease(nextRelease) && !issue.containsFixRelease(release)) ret.add(issue);
		}
		return ret;
	}

	public Set<Issue> getOpenBugs() {
		return issueDao.getOpenBugs(this);
	}

	public Set<Issue> getOpenIdeas() {
		return issueDao.getOpenIdeas(this);
	}

	public Set<RequirementEstimationVote> getRequirementEstimationVotes() {
		Set<RequirementEstimationVote> ret = new HashSet<RequirementEstimationVote>();
		for (Requirement requirement : getRequirements()) {
			ret.addAll(requirement.getEstimationVotes());
		}
		return ret;
	}

	public Set<SprintDaySnapshot> getSprintDaySnapshots() {
		Set<SprintDaySnapshot> ret = new HashSet<SprintDaySnapshot>();
		for (Sprint sprint : getSprints()) {
			ret.addAll(sprint.getDaySnapshots());
		}
		return ret;
	}

	public Set<SprintDaySnapshot> getExistingSprintDaySnapshots() {
		Set<SprintDaySnapshot> ret = new HashSet<SprintDaySnapshot>();
		for (Sprint sprint : getSprints()) {
			ret.addAll(sprint.getExistingDaySnapshots());
		}
		return ret;
	}

	public Set<Comment> getLatestComments() {
		return getComments(true);
	}

	private Set<Comment> getComments(boolean latestOnly) {
		Set<Comment> ret = new HashSet<Comment>();
		ret.addAll(getComments(Arrays.asList(this), latestOnly));
		ret.addAll(getComments(getSprints(), latestOnly));
		ret.addAll(getComments(getParticipants(), latestOnly));
		ret.addAll(getComments(getRequirements(), latestOnly));
		ret.addAll(getComments(getQualitys(), latestOnly));
		ret.addAll(getComments(getTasks(), latestOnly));
		ret.addAll(getComments(getImpediments(), latestOnly));
		ret.addAll(getComments(getIssues(), latestOnly));
		ret.addAll(getComments(getRisks(), latestOnly));
		ret.addAll(getComments(getWikipages(), latestOnly));
		ret.addAll(getComments(getSimpleEvents(), latestOnly));
		ret.addAll(getComments(getFiles(), latestOnly));
		ret.addAll(getComments(getReleases(), latestOnly));
		ret.addAll(getComments(getSprintSnapshots(), latestOnly));
		ret.addAll(getComments(getRequirementEstimationVotes(), latestOnly));
		ret.addAll(getComments(getUserConfigs(), latestOnly));
		ret.addAll(getComments(getProjectEvents(), latestOnly));
		ret.addAll(getComments(getSubjects(), latestOnly));
		ret.addAll(getComments(getReleases(), latestOnly));
		ret.addAll(getComments(getBlogEntrys(), latestOnly));
		return ret;
	}

	private Set<Comment> getComments(Collection<? extends AEntity> entities, boolean latestOnly) {
		Set<Comment> ret = new HashSet<Comment>();
		for (AEntity entity : entities) {
			Set<Comment> comments = commentDao.getCommentsByParent(entity);
			ret.addAll(latestOnly ? getLatest(comments) : comments);
		}
		return ret;
	}

	private Set<Comment> getLatest(Set<Comment> comments) {
		if (comments.size() < 2) return comments;
		Comment latest = null;
		for (Comment comment : comments) {
			DateAndTime dateAndTime = comment.getDateAndTime();
			if (latest == null || dateAndTime.isAfter(latest.getDateAndTime())) latest = comment;
		}
		assert latest != null;
		return Utl.toSet(latest);
	}

	@Override
	public String toString() {
		return getLabel();
	}

	@Override
	public void ensureIntegrity() {
		super.ensureIntegrity();
		addParticipants(getAdmins());
		addParticipants(getProductOwners());
		addParticipants(getScrumMasters());
		addParticipants(getTeamMembers());
		if (!isCurrentSprintSet()) {
			Sprint sprint = sprintDao.newEntityInstance();
			sprint.setProject(this);
			sprintDao.saveEntity(sprint);
			setCurrentSprint(sprint);
		}
		if (!isNextSprintSet()) {
			createNextSprint();
		}
		updateRequirementsOrder();
		if (!isPunishmentUnitSet()) {
			setPunishmentUnit(Money.EUR);
		}
		if (getPunishmentFactor() == 0) {
			setPunishmentFactor(1);
		}
		if (!isHomepageDirSet()) setAutoUpdateHomepage(false);
		if (!isIssueReplyTemplateSet()) setIssueReplyTemplate(createDefaultIssueReplyTemplate());
		if (!isSubscriberNotificationTemplateSet())
			setSubscriberNotificationTemplate(createDefaultSubscriberNotificationTemplate());
	}

	private void updateRequirementsOrder() {
		List<Requirement> requirements = new ArrayList<Requirement>(getProductBacklogRequirements());
		Collections.sort(requirements, getRequirementsOrderComparator());
		setRequirementsOrderIds(AEntity.getIdsAsList(requirements));
	}

	private String createDefaultIssueReplyTemplate() {
		StringBuilder sb = new StringBuilder();
		sb.append("Hello ${issuer.name},\n");
		sb.append("\n");
		sb.append("thank you very much for your feedback.\n");
		sb.append("\n");
		sb.append("Your issue is now known as ${issue.reference}. You can view it on our homepage: ${homepage.url}/${issue.reference}.html\n");
		return sb.toString();
	}

	private String createDefaultSubscriberNotificationTemplate() {
		StringBuilder sb = new StringBuilder();
		sb.append("Hello,\n");
		sb.append("\n");
		sb.append("there is news on an entity you are subscribed to:\n");
		sb.append("\n");
		sb.append("    ${entity.reference} ${entity.label}\n");
		sb.append("    ${homepage.url}/${entity.reference}.html\n");
		sb.append("\n");
		sb.append("    ${change.message}\n");
		sb.append("\n");
		sb.append("---\n");
		sb.append("Unsubscribe from ${entity.reference}: ${unsubscribe.url}\n");
		sb.append("\n");
		sb.append("Unsubscribe from all entities: ${unsubscribeall.url}\n");
		return sb.toString();
	}

	@Override
	public boolean isVisibleFor(User user) {
		return (user != null && user.isAdmin()) || containsParticipant(user) || containsAdmin(user);
	}

	public boolean isEditableBy(User user) {
		return isVisibleFor(user);
	}

	@Override
	public boolean isDeletableBy(User user) {
		if (user != null && user.isAdmin()) return true;
		return containsAdmin(user);
	}

	// --- test data ---

	public void addTestScenarios() {
		Scenario sce = null;

		// SetQualities to Scenarios

		ArrayList<Quality> auxQualities = new ArrayList<Quality>(this.getQualitys());

		ArrayList<Quality> aux0 = new ArrayList<Quality>();
		for (Quality q : auxQualities) {
			if ("Usability".equals(q.getLabel())) aux0.add(q);
			if ("Modificability".equals(q.getLabel())) aux0.add(q);
		}

		ArrayList<Quality> aux1 = new ArrayList<Quality>();
		for (Quality q : auxQualities)
			if ("Usability".equals(q.getLabel())) aux1.add(q);

		ArrayList<Quality> aux2 = new ArrayList<Quality>();
		for (Quality q : auxQualities)
			if ("Security".equals(q.getLabel())) aux2.add(q);

		ArrayList<Quality> aux3 = new ArrayList<Quality>();
		for (Quality q : auxQualities)
			if ("Performance".equals(q.getLabel())) aux3.add(q);

		// Set Requirements to Scenarios

		ArrayList<Requirement> auxRequirement = new ArrayList<Requirement>(this.getRequirements());

		ArrayList<Requirement> auxR0 = new ArrayList<Requirement>();
		for (Requirement r : auxRequirement) {
			if ("Add items to shopping cart".equals(r.getLabel())) auxR0.add(r);
			if ("Remove items from shopping cart".equals(r.getLabel())) auxR0.add(r);
		}

		ArrayList<Requirement> auxR1 = new ArrayList<Requirement>();
		for (Requirement r : auxRequirement) {
			if ("Edit wish list".equals(r.getLabel())) auxR0.add(r);
			if ("Create wish list".equals(r.getLabel())) auxR0.add(r);
		}

		ArrayList<Requirement> auxR2 = new ArrayList<Requirement>();
		for (Requirement r : auxRequirement) {
			if ("Investigate improve card validation".equals(r.getLabel())) auxR2.add(r);
		}

		ArrayList<Requirement> auxR3 = new ArrayList<Requirement>();
		for (Requirement r : auxRequirement) {
			if ("Improve transaction processing".equals(r.getLabel())) auxR3.add(r);
		}

		// Scenario 1
		sce = scenarioDao.postScenario(this, "Scenario Shopping Cart");
		sce.setCriticism(scrum.client.project.Scenario.CRITICISM_VALUES[2]);
		sce.setCriticismValue(scrum.client.project.Scenario.CRITICISM_INT_VALUES[2]);
		sce.setQualitys(aux0);
		sce.setStoriesAssociates(auxR0);
		sce.setPriority(1);
		sce.setState(scrum.client.project.Scenario.PROJECT_STATE_VALUES[2]);
		sce.setStimulus("The customer wants to add and remove items from shopping cart.");
		sce.setSourceOfStimulus("Customer.");
		sce.setEnvironment("The customer is planning a purchase.");
		sce.setArtifactStimulated("System Application.");
		sce.setResponse("The customer is allowed to add and remove items.");
		sce.setResponseMeasure("In 2 or 3 clicks, the customer has his shopping cart updated. The changes should not impact in a big way on the system.");

		// Scenario 2
		sce = scenarioDao.postScenario(this, "Scenario Wish List");
		sce.setCriticism(scrum.client.project.Scenario.CRITICISM_VALUES[1]);
		sce.setCriticismValue(scrum.client.project.Scenario.CRITICISM_INT_VALUES[1]);
		sce.setQualitys(aux1);
		sce.setStoriesAssociates(auxR1);
		sce.setPriority(1);
		sce.setState(scrum.client.project.Scenario.PROJECT_STATE_VALUES[2]);
		sce.setStimulus("The customer wants to add and remove items from the wish list.");
		sce.setSourceOfStimulus("Customer.");
		sce.setEnvironment("The customer is searching for future purchases.");
		sce.setArtifactStimulated("System Application.");
		sce.setResponse("The customer can save a wished item.");
		sce.setResponseMeasure("Updating wish list is not a tedious activity.");

		// Scenario 3
		sce = scenarioDao.postScenario(this, "Scenario Card Validation");
		sce.setCriticism(scrum.client.project.Scenario.CRITICISM_VALUES[0]);
		sce.setCriticismValue(scrum.client.project.Scenario.CRITICISM_INT_VALUES[0]);
		sce.setQualitys(aux2);
		sce.setStoriesAssociates(auxR2);
		sce.setPriority(2);
		sce.setState(scrum.client.project.Scenario.PROJECT_STATE_VALUES[1]);
		sce.setStimulus("The System wants to validate the purchase.");
		sce.setSourceOfStimulus("Purchase System.");
		sce.setEnvironment("The customer confirm the purchase and the system should validate the credit card payment.");
		sce.setArtifactStimulated("System Application.");
		sce.setResponse("Credit card is validated or not.");
		sce.setResponseMeasure("False acceptance rate should be less than 0,01%.");

		// Scenario 4
		sce = scenarioDao.postScenario(this, "Scenario Tansaction Processing");
		sce.setCriticism(scrum.client.project.Scenario.CRITICISM_VALUES[1]);
		sce.setCriticismValue(scrum.client.project.Scenario.CRITICISM_INT_VALUES[1]);
		sce.setQualitys(aux3);
		sce.setStoriesAssociates(auxR3);
		sce.setPriority(2);
		sce.setState(scrum.client.project.Scenario.PROJECT_STATE_VALUES[1]);
		sce.setStimulus("The Client wants to improve the transactions.");
		sce.setSourceOfStimulus("Client.");
		sce.setEnvironment("The purchase registration is taking more time than expected.");
		sce.setArtifactStimulated("System Application.");
		sce.setResponse("Purchase Registration will improve.");
		sce.setResponseMeasure("The transaction time should improve by 40%.");

		// Related Scenarios on Requirements

		ArrayList<Scenario> auxScenarios = new ArrayList<Scenario>(getScenarios());

		ArrayList<Scenario> auxS0 = new ArrayList<Scenario>();

		ArrayList<Scenario> auxS1 = new ArrayList<Scenario>();

		ArrayList<Scenario> auxS2 = new ArrayList<Scenario>();

		ArrayList<Scenario> auxS3 = new ArrayList<Scenario>();

		for (Requirement r : auxRequirement) {
			if (("Edit wish list".equals(r.getLabel())) || ("Create wish list".equals(r.getLabel()))) {
				for (Scenario s : auxScenarios) {
					if ("Scenario Wish List".equals(s.getLabel())) auxS0.add(s);
				}
				r.setRelatedScenarios(auxS0);
			}
			if (("Add items to shopping cart".equals(r.getLabel()))
					|| ("Remove items from shopping cart".equals(r.getLabel()))) {
				for (Scenario s : auxScenarios) {
					if ("Scenario Shopping Cart".equals(s.getLabel())) auxS1.add(s);
				}
				r.setRelatedScenarios(auxS1);
			}
			if ("Improve transaction processing".equals(r.getLabel())) {
				for (Scenario s : auxScenarios) {
					if ("Scenario Tansaction Processing".equals(s.getLabel())) auxS2.add(s);
				}
				r.setRelatedScenarios(auxS2);
			}
			if ("Investigate improve card validation".equals(r.getLabel())) {
				for (Scenario s : auxScenarios) {
					if ("Scenario Card Validation".equals(s.getLabel())) auxS3.add(s);
				}
				r.setRelatedScenarios(auxS3);
			}
		}
	}

	public void addTestImpediments() {
		Impediment imp = null;

		// Apache's version
		imp = impedimentDao.postImpediment(this, randomPast(5), "Apache's version is obsolete", false);
		imp.setDescription("The current version of Apache is obsolete.");
		imp.setSolution("Upgrade Apache's version.");
	}

	private static Date randomPast(int beforeMaxDays) {
		return Date.beforeDays(Utl.randomInt(0, beforeMaxDays));
	}

	public void addTestRisks() {
		Risk rsk = null;

		// Host
		rsk = riskDao.postRisk(this, "The web server hosting could not offer the availability that system requires",
			40, 100);
		rsk.setProbabilityMitigation("Ask to the hosting provider for an exclusive bandwidth.");
		rsk.setImpactMitigation("Look for another one and migrate all the systems that already exist.");

		// Apache
		rsk = riskDao.postRisk(this, "Apache Version coverage", 20, 100);
		rsk.setProbabilityMitigation("Low probability. New version should improve oldest.");
		rsk.setImpactMitigation("Some function could be unsupported by the new version. Should migrate to another server.");

	}

	public void addTestSimpleEvents() {
		// let people generate their own events
	}

	public void addTestEvents() {
		// no test events
	}

	public void addTestIssues() {
		Issue iss = null;

		// Reggistration button
		iss = issueDao.postIssue(this, "Add a registration button");
		iss.setDescription("Add a button to allow user registration.");
		iss.setCloseDate(Date.beforeDays(1));
	}

	public void addTestReleases() {
		Release r1 = releaseDao.postRelease(this, Date.beforeDays(30), "1.0");
		r1.setReleased(true);

		Release r2 = releaseDao.postRelease(this, Date.inDays(5), "1.1");
		r2.addSprint(getCurrentSprint());
	}

	public void addTestRequirements() {
		Requirement req = null;
		List<Requirement> reqOrder = new LinkedList<Requirement>();

		// Add items to shopping cart
		req = requirementDao.postRequirement(this, "Add items to shopping cart", 5f);
		req.setDescription("As a customer, I want to add a book to my shopping cart.");
		reqOrder.add(req);
		req.setSprint(getCurrentSprint());
		taskDao.postTask(req, "Modify database", 5);
		taskDao.postTask(req, "Create webpage (UI)", 8);
		taskDao.postTask(req, "Create webpage (Javascript logic)", 13);
		taskDao.postTask(req, "Write automated acceptance tests", 13);
		taskDao.postTask(req, "Update buyer help webpage", 3);

		// Remove items from shopping cart
		req = requirementDao.postRequirement(this, "Remove items from shopping cart", 2f);
		req.setDescription("As a customer, I want to remove a book from my shopping cart.");
		reqOrder.add(req);
		req.setSprint(getCurrentSprint());

		// Improve transaction processing
		req = requirementDao.postRequirement(this, "Improve transaction processing", 13f);
		req.setDescription("Improve the performance off the transaction processing.");
		reqOrder.add(req);
		taskDao.postTask(req, "Merge DCP code and complete layer-level tests", 5);
		taskDao.postTask(req, "Complete machine order for pRank", 8);
		taskDao.postTask(req, "Change DCP and reader to use pRank http API", 13);

		// Investigate improve card validation
		req = requirementDao.postRequirement(this, "Investigate improve card validation", 20f);
		req.setDescription("Investigate a solution to improve the speed of the credit card validation.");
		reqOrder.add(req);

		// Update servers
		req = requirementDao.postRequirement(this, "Update servers", 13f);
		req.setDescription("Update all the servers to Apache 2.2.3.");
		reqOrder.add(req);

		// Create wish list
		req = requirementDao.postRequirement(this, "Create wish list", 40f);
		req.setDescription("As a customer, I want to create and save a wish list.");
		reqOrder.add(req);

		// Edit wish list
		req = requirementDao.postRequirement(this, "Edit wish list", 20f);
		req.setDescription("As a customer, I want to add and remove articles in my wish list.");
		reqOrder.add(req);

		// updateRequirementsOrder(reqOrder);
	}

	public void addTestQualitys() {
		Quality qly = null;

		qly = qualityDao.postQuality(this, "Performance");
		qly = qualityDao.postQuality(this, "Security");
		qly = qualityDao.postQuality(this, "Availability");
		qly = qualityDao.postQuality(this, "Modificability");
		qly = qualityDao.postQuality(this, "Usability");
		qly = qualityDao.postQuality(this, "Reliability");
	}

	public void addTestSprints() {
		sprintDao.createTestHistorySprint(this, Date.beforeDays(45), Date.beforeDays(15));
		sprintDao.createTestSprint(this);
	}

	public Comparator<Requirement> getRequirementsOrderComparator() {
		if (requirementsOrderComparator == null) requirementsOrderComparator = new Comparator<Requirement>() {

			@Override
			public int compare(Requirement a, Requirement b) {
				if (a.isInCurrentSprint() && !b.isInCurrentSprint()) return -1;
				if (b.isInCurrentSprint() && !a.isInCurrentSprint()) return 1;
				List<String> order = getRequirementsOrderIds();
				int additional = order.size();
				int ia = order.indexOf(a.getId());
				if (ia < 0) {
					ia = additional;
					additional++;
				}
				int ib = order.indexOf(b.getId());
				if (ib < 0) {
					ib = additional;
					additional++;
				}
				return ia - ib;
			}
		};
		return requirementsOrderComparator;
	}

	public Quality getQualityByLabel(String label) {
		for (Quality quality : getQualitys()) {
			if (quality.isLabel(label)) return quality;
		}
		return null;
	}

}
